package com.worldwizards.smut.serialization;

import java.io.IOException;
import java.io.ObjectStreamConstants;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class SerializedObjectRoot {
	static final int HANDLE_BASE = 0x7E0000;
	private int handleCounter;
	private List<ParseObject> rootObjects = new ArrayList<ParseObject>();
	Map<Integer, ParseObject> objectRegistry = new HashMap<Integer, ParseObject>();

	public SerializedObjectRoot(BinaryParser parser) throws IOException,
			SerializationParsingException {
		reset();
		short magic = parser.readShort();
		if (ObjectStreamConstants.STREAM_MAGIC != magic) {
			throw new SerializationParsingException("Invalid magic number: "+Integer.toHexString(0xFFFF&magic));
		}
		short version = parser.readShort();
		if (5 != version) {
			throw new SerializationParsingException("Bad protocol version: "
					+ version);
		}
		parseContents(parser);
	}

	private int getNewHandle() {
		return handleCounter++;
	}

	private void addRootObject(ParseObject obj) {
		rootObjects.add(obj);
	}
	
	private void register(ParseObject obj){
		objectRegistry.put(obj.getHandle(),obj);
	}
	
	private ParseObject lookup(int handle){
		return objectRegistry.get(handle);
	}
	
	private void parseContents(BinaryParser parser) throws IOException,
			SerializationParsingException {
		parser.mark();
		while (parser.readByte() != -1) {
			parser.rewind();
			addRootObject(parseContent(parser));
			parser.mark();
		}
	}

	public ParseObject parseContent( BinaryParser parser) throws IOException,
			SerializationParsingException {
		parser.mark();
		byte b = parser.readByte();
		switch (b) {
			case ObjectStreamConstants.TC_OBJECT:
				SerializedClassRecord cd = parseClassDesc(parser);
				int newHandle = getNewHandle();
				SerializedObjectRecord sr = new SerializedObjectRecord(this,parser,
					newHandle, cd);
				register(sr);
				return sr;
			case ObjectStreamConstants.TC_CLASS:
				cd = parseClassDesc(parser);
				newHandle = getNewHandle();
				cd.setHandle(newHandle);
				register(cd);
				return cd;
			case ObjectStreamConstants.TC_ARRAY:
				cd = parseClassDesc(parser);
				newHandle = getNewHandle();
				int arraySize = parser.readInt();
				SerializedArrayRecord sar = new SerializedArrayRecord(this,parser,newHandle,cd,arraySize);
				register(sar);
				return sar;
			case ObjectStreamConstants.TC_STRING:
				newHandle = getNewHandle();
				String str = parser.readUTF();
				SerializedStringRecord ssr = new SerializedStringRecord(newHandle,str);
				register(ssr);
				return ssr;
			case ObjectStreamConstants.TC_CLASSDESC:
			case ObjectStreamConstants.TC_PROXYCLASSDESC:
				parser.rewind();
				SerializedClassRecord pobj = parseNewClassDesc(parser);
				register(pobj);
				return pobj;
			case ObjectStreamConstants.TC_REFERENCE:
				int handle = parser.readInt();
				return lookup(handle);
			case ObjectStreamConstants.TC_NULL:
				return null;
			case ObjectStreamConstants.TC_EXCEPTION:
				reset();
				ParseObject obj = parseContent(parser);
				reset();
				return obj;
			case ObjectStreamConstants.TC_RESET:
				reset();
				return parseContent(parser);
			default:
				throw new SerializationParsingException("Encountered unexpected opcode: "+Integer.toHexString(0xFF&b));
		}
	}
	
	public SerializedClassRecord parseClassDesc(BinaryParser parser) throws IOException, SerializationParsingException{
		byte b = parser.peekByte();
		switch(b){
			case ObjectStreamConstants.TC_CLASSDESC:
			case ObjectStreamConstants.TC_PROXYCLASSDESC: 
				return parseNewClassDesc(parser);
			case ObjectStreamConstants.TC_NULL:
				parser.readByte();
				return null;
			case ObjectStreamConstants.TC_REFERENCE:
				parser.readByte();
				int handle = parser.readInt();
				return (SerializedClassRecord)lookup(handle);
			default:
				throw new SerializationParsingException("found unexpected opcode: "+Integer.toHexString(0xFF&b));
				
		}
	}


	private SerializedClassRecord parseNewClassDesc(BinaryParser parser)
			throws IOException, SerializationParsingException {
		byte b = parser.readByte();
		switch (b) {
		case ObjectStreamConstants.TC_CLASSDESC:
			String name = parser.readUTF();
			long serialVersionUID = parser.readLong();
			int handle = getNewHandle();
			return new SerializedClassRecord(this,name,handle,serialVersionUID,parser);
		/*case ObjectStreamConstants.TC_PROXYCLASSDESC:
			handle = getNewHandle();
			int interfaceCount = parser.readInt();
			List<String> ifaceList = new ArrayList<String>();
			for (int i = 0; i < interfaceCount; i++) {
				ifaceList.add(parser.readUTF());
			}
			ClassAnnotation cannotation = new ClassAnnotation(parser);
			SerializedClassRecord superClass = parseClassDesc(parser); 
			return new SerializedClassRecord(handle, new ProxyClassDescription(ifaceList, handle, cannotation, superClass));
		*/default:
			throw new SerializationParsingException("Unexpected byte: " + b);
		}
	}

	public void reset() {
		handleCounter = HANDLE_BASE;
	}

	public void dump() {
		for (ParseObject po : rootObjects){
			po.dump("");
		}
		
	}

}
